#include <utils/traceUtils/registerValue.idc>
/**
 * 属性：
 * public:
 *      this.canDo            代表是否初始化完成，值为0或者1，为0代表初始化失败，为1代表初始化成功
 * private:
 *      this.real_addr_fp     输出主文件真实地址的句柄， 加偏移后的地址
 *      this.orig_addr_fp     输出主文件原始地址的句柄， 不加偏移后的地址
 * 方法：
 * public:
*/
class OutputUtils{
    /**
     * 构造方法参数说明
     * aslAddr                  偏移地址
     * baseOutputFileName       输出文件夹全路径
     * outputFileName           输出文件名
     */
    OutputUtils(aslAddr, baseOutputFileName, outputFileName){
         this.ASL_addr = aslAddr;
         this.baseOutputFileName = baseOutputFileName;
         this.outputFileName = outputFileName;
         this.canDo = 1;

         if(this.getFileHandle() == 0){
            Warning("open file fail ！！！ \n");
            this.canDo = 0;
            return;
        }

        this.registerValue = RegisterValue();
        this.instructionsBCount = 0x1;
    }

    ~OutputUtils(){
        fclose(this.real_addr_fp);
        fclose(this.orig_addr_fp);
        fclose(this.orig_addr_step_param_fp);
        fclose(this.real_addr_step_param_fp);
        fclose(this.register_fp);
        fclose(this.bDetail_fp);
        fclose(this.all_log_fp);
    }

    writeToFile(content){
        fprintf(this.real_addr_fp, content);
        fprintf(this.orig_addr_fp, content);
    }

    getFileHandle(){
        Message("output real addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_real_addr.log");
        this.real_addr_fp = fopen(this.baseOutputFileName + this.outputFileName + "_real_addr.log", "w");
        if(this.real_addr_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_orig_addr.log");
        this.orig_addr_fp = fopen(this.baseOutputFileName + this.outputFileName + "_orig_addr.log", "w");
        if(this.orig_addr_fp == 0){
            return 0;
        }

        Message("output orig addr step param file path: %s\n", this.baseOutputFileName + this.outputFileName + "_orig_addr_step_param.log");
        this.orig_addr_step_param_fp = fopen(this.baseOutputFileName + this.outputFileName + "_orig_addr_step_param.log", "w");
        if(this.orig_addr_step_param_fp == 0){
            return 0;
        }

        Message("output real addr step param file path: %s\n", this.baseOutputFileName + this.outputFileName + "_real_addr_step_param.log");
        this.real_addr_step_param_fp = fopen(this.baseOutputFileName + this.outputFileName + "_real_addr_step_param.log", "w");
        if(this.real_addr_step_param_fp == 0){
            return 0;
        }

        Message("output register file path: %s\n", this.baseOutputFileName + this.outputFileName + "_register.log");
        this.register_fp = fopen(this.baseOutputFileName + this.outputFileName + "_register.log", "w");
        if(this.register_fp == 0){
            return 0;
        }

        Message("output b detail file path: %s\n", this.baseOutputFileName + this.outputFileName + "_b_detail.log");
        this.bDetail_fp = fopen(this.baseOutputFileName + this.outputFileName + "_b_detail.log", "w");
        if(this.bDetail_fp == 0){
            return 0;
        }

        Message("output all file path: %s\n", this.baseOutputFileName + this.outputFileName + "_all.log");
        this.all_log_fp = fopen(this.baseOutputFileName + this.outputFileName + "_all.log", "w");
        if(this.all_log_fp == 0){
            return 0;
        }
        return 1;
    }

    stringComplementaryLength(str, len){
        auto lenTmp, differenceLen, spaceStr, i;
        lenTmp = strlen(str);
        if(lenTmp > len){
            return str;
        }

        differenceLen = len - lenTmp;
        spaceStr = "";
        for(i = 0; i < differenceLen; i++){
            spaceStr = spaceStr + " ";
        }
        return str + spaceStr;
    }

    outputByAddr(addr, instructionsCount){
        auto inst, disAsm, comment, addrSegmentName, pid, tid;
        inst = FindCode(PrevAddr(addr), SEARCH_DOWN | SEARCH_NEXT);
        disAsm = GetDisasm(inst);
        addrSegmentName = SegName(addr);
        addrSegmentName = this.stringComplementaryLength(addrSegmentName, 13);
        disAsm = this.stringComplementaryLength(disAsm, 50);

        pid = GetEventPid();
        tid = GetEventTid();

        this.outputRegister(addr, instructionsCount, pid, tid);
        this.outputBFunc(addr, instructionsCount, disAsm, pid, tid);
        this.outputAddParamAll(pid, tid, instructionsCount, addrSegmentName, addr, disAsm);
    }

    outputAddParamAll(pid, tid, instructionsCount, addrSegmentName, addr, disAsm){
        auto logPrefix, origLogPrefix;
        logPrefix = form("[%x] [%x] [%08x] [%s] 0x%x: %s    ## ", pid, tid, instructionsCount, addrSegmentName, addr, disAsm);
        origLogPrefix = form("[%x] [%x] [%08x] [%s] 0x%x: %s    ## ", pid, tid, instructionsCount, addrSegmentName, addr - this.ASL_addr, disAsm);
        if(GetOperandValue(addr, 4) != 0xffffffffffffffff){
            this.outputAddParam(addr, 5, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 3) != 0xffffffffffffffff){
            this.outputAddParam(addr, 4, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 2) != 0xffffffffffffffff){
            this.outputAddParam(addr, 3, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 1) != 0xffffffffffffffff){
            this.outputAddParam(addr, 2, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 0) != 0xffffffffffffffff){
            this.outputAddParam(addr, 1, logPrefix, origLogPrefix);
            return 1;
        }

        this.outputAddParam(addr, 0, logPrefix, origLogPrefix);
        return 1;
    }

    outputBFunc(addr, instructionsCount, disAsm, pid, tid){
        auto mnem = GetMnem(addr);
        if(!(strstr(mnem, "B") != -1 && mnem != "SUB" && mnem != "BKPT" && mnem != "BIC" && mnem != "QSUB" && mnem != "QDSUB" && mnem != "RSB" && mnem != "SBC")){
            return;
        }
        
        fprintf(this.bDetail_fp, "[%x] [%x] [%08x] - INSN: [%08x] - %s\n", pid, tid, this.instructionsBCount, instructionsCount, disAsm);
        auto i;
        for(i = 0; i < 32; i = i + 2){
            fprintf(this.bDetail_fp, "    X%d: %s, X%d: %s\n", i, this.registerValue.getValue(i, 80), i + 1, this.registerValue.getValue(i + 1, 80));
        }
        fprintf(this.bDetail_fp, "\n");
        this.instructionsBCount = this.instructionsBCount + 1;
    }

    outputRegister(addr, instructionsCount, pid, tid){
        fprintf(this.register_fp, "[%x] [%x] [%08x]:\n", pid, tid, instructionsCount);

        auto i;
        for(i = 0; i < 32; i = i + 2){
            fprintf(this.register_fp, "    X%d: %s, X%d: %s\n", i, this.registerValue.getValue(i, 80), i + 1, this.registerValue.getValue(i + 1, 80));
        }

        fprintf(this.register_fp, "\n");
    }

    outputAddParam(addr, count, logPrefix, origLogPrefix){
        Message("%s%s\n", logPrefix, this.paramValue(addr, count));
        fprintf(this.real_addr_fp, "%s%s\n", logPrefix, this.paramValue(addr, count));
        fprintf(this.orig_addr_fp, "%s%s\n", origLogPrefix, this.paramValue(addr, count));
        fprintf(this.orig_addr_step_param_fp, "%s%s\n", origLogPrefix, this.paramRealValue(addr, count));
        fprintf(this.real_addr_step_param_fp, "%s%s\n", logPrefix, this.paramRealValue(addr, count));
        auto allRegisterValue = this.getAllRegisterValue();
        fprintf(this.all_log_fp, "%s%s\n%s", origLogPrefix, this.paramValue(addr, count), allRegisterValue);
    }

    getAllRegisterValue(){
        auto i;
        auto result = "";
        for(i = 0; i <= 30; i++){
            result = form("%s    X%02d: addr: %s, name: %s value: %s\n", result, i, this.registerValue.getAddr(i), this.registerValue.getName(i), this.registerValue.getValue(i, 80));
        }

        result = form("%s    %s: addr: %s\n", result, "SP", this.registerValue.getAddr(31));
        result = form("%s    %s: addr: %s\n", result, "PC", this.registerValue.getAddr(32));
        return result;
    }

    paramRealValue(addr, count){
        auto i, valueType, realValue, addrType;
        auto realResult = "";

        addrType = this.getAddrType(addr);
        if(addrType == 1){
            realResult = form("X1=%s", this.registerValue.getSendMsgValue("X1", 80));
            return realResult;
        }

        for(i = 0; i < count; i++){
            valueType = GetOpnd(addr, i);
            realValue = this.registerValue.getValue(valueType, 80);
            if(realResult == ""){
                realResult = form("%s=%s", valueType, realValue);
                continue;
            }

            realResult = form("%s, %s=%s", realResult, valueType, realValue);
        }
        return realResult;
    }

    paramValue(addr, count){
        auto i, valueType, realValue, addrType;
        auto result = "";
        for(i = 0; i < count; i++){
            valueType = GetOpnd(addr, i);
            if(result == ""){
                result = form("%s=0x%x", valueType, GetOperandValue(addr, i));
                continue;
            }

            result = form("%s, %s=0x%x", result, valueType, GetOperandValue(addr, i));
        }

        addrType = this.getAddrType(addr);
        if(addrType == 1){
            result = form("%s, X1=%s", result, this.registerValue.getSendMsgValue("X1", 80));
        }
        return result;
    }

    /*
    type value
    1         B BL    _objc_msgSend
    */
    getAddrType(addr){
        auto mnem, inst, disAsm;
        mnem = GetMnem(addr);
        if(mnem == "BL" || mnem == "B"){
            inst = FindCode(PrevAddr(addr), SEARCH_DOWN | SEARCH_NEXT);
            disAsm = GetDisasm(inst);
            if(strstr(disAsm, "objc_msgSend") != -1){
                return 1;
            }
        }
        return 0;
    }
}